package cn.yh.nft.turbo.chain.domain.service.impl;

import cn.hutool.http.HttpRequest;
import cn.yh.nft.turbo.api.chain.constant.ChainOperateBizTypeEnum;
import cn.yh.nft.turbo.api.chain.constant.ChainOperateTypeEnum;
import cn.yh.nft.turbo.api.chain.constant.ChainType;
import cn.yh.nft.turbo.api.chain.request.ChainProcessRequest;
import cn.yh.nft.turbo.api.chain.request.ChainQueryRequest;
import cn.yh.nft.turbo.api.chain.response.ChainProcessResponse;
import cn.yh.nft.turbo.api.chain.response.data.ChainCreateData;
import cn.yh.nft.turbo.api.chain.response.data.ChainOperationData;
import cn.yh.nft.turbo.api.chain.response.data.ChainResultData;
import cn.yh.nft.turbo.base.exception.RepoErrorCode;
import cn.yh.nft.turbo.base.exception.SystemException;
import cn.yh.nft.turbo.chain.domain.constant.ChainCodeEnum;
import cn.yh.nft.turbo.chain.domain.constant.ChainOperateStateEnum;
import cn.yh.nft.turbo.chain.domain.constant.WenChangChainConfiguration;
import cn.yh.nft.turbo.chain.domain.entity.*;
import cn.yh.nft.turbo.chain.domain.response.ChainResponse;
import cn.yh.nft.turbo.chain.domain.service.AbstractChainService;
import cn.yh.nft.turbo.chain.infrastructure.utils.WenChangChainUtils;
import com.alibaba.fastjson2.JSON;
import com.esotericsoftware.minlog.Log;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.math.ec.WNafL2RMultiplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;

import static cn.yh.nft.turbo.api.common.constant.CommonConstant.APP_NAME_UPPER;
import static cn.yh.nft.turbo.api.common.constant.CommonConstant.SEPARATOR;
import static cn.yh.nft.turbo.chain.infrastructure.utils.WenChangChainUtils.configureHeaders;

/**
 * @ClassName: WenChangChainServiceImpl
 * @Author: 草莓爱上啵啵
 */
@Service("wenChangChainService")
@Slf4j
public class WenChangChainServiceImpl extends AbstractChainService {

    @Autowired
    protected WenChangChainConfiguration wenChangChainConfiguration;

    /**
     * 重写doPost方法以处理链式请求中的POST操作
     * 该方法构造一个HTTP POST请求，发送到指定的主机和路径，并处理响应
     *
     * @param chainRequest 包含请求详细信息的链式请求对象
     * @return 返回链式响应对象，包含响应代码、消息和成功标志
     */
    @Override
    protected ChainResponse doPost(ChainRequest chainRequest) {
        // 构造POST请求的URL
        HttpRequest post = HttpRequest.post(chainRequest.getHost() + chainRequest.getPath());

        // 配置请求头，包括签名、当前时间和API密钥
        post.addHeaders(configureHeaders(chainRequest.getSignature(), chainRequest.getCurrentTime(),
                wenChangChainConfiguration.getApiKey()));

        // 设置请求体为JSON格式的请求数据
        post.body(JSON.toJSONString(chainRequest.getBody()));

        // 初始化响应字符串和结果对象
        String responseStr = "";
        ChainResponse result = new ChainResponse();

        // 发送请求并获取响应
        try {
            responseStr = post.execute().body();
        } catch (Exception e) {
            // 记录错误日志，包括请求和响应内容
            log.error("http client error, request: {}, response: {}", chainRequest, responseStr);

            // 设置错误的响应代码、消息和成功标志为false
            result.setResponseCode(ChainCodeEnum.CHAIN_POST_ERROR.name());
            result.setResponseMessage(e.getMessage());
            result.setSuccess(false);

            // 返回错误响应
            return result;
        }

        // 解析响应字符串为ChainResponse对象并返回
        return JSON.parseObject(responseStr, ChainResponse.class);
    }

    /**
     * 重写doDelete方法以处理链式请求中的删除操作
     * 该方法构造一个HTTP DELETE请求，发送给指定的主机和路径，并处理返回的响应
     *
     * @param chainRequest 链式请求对象，包含请求的所有必要信息
     * @return 返回链式响应对象，包含处理结果和相关信息
     */
    @Override
    protected ChainResponse doDelete(ChainRequest chainRequest) {
        // 构造HTTP DELETE请求
        HttpRequest delete = HttpRequest.delete(chainRequest.getHost() + chainRequest.getPath());
        // 配置请求头，包括签名、当前时间和API密钥
        delete.addHeaders(configureHeaders(chainRequest.getSignature(), chainRequest.getCurrentTime(),
                wenChangChainConfiguration.getApiKey()));
        // 设置请求体，使用JSON格式序列化请求体内容
        delete.body(JSON.toJSONString(chainRequest.getBody()));

        // 初始化响应字符串
        String responseStr = "";
        // 创建链式响应对象以存储处理结果
        ChainResponse result = new ChainResponse();
        try {
            // 发送请求并获取响应体内容
            responseStr = delete.execute().body();
        } catch (Exception e) {
            // 记录错误日志，包括请求和响应内容
            log.error("http client error, request: {}, response: {}", chainRequest, responseStr);
            // 设置响应码、消息和成功状态为false
            result.setResponseCode(ChainCodeEnum.CHAIN_POST_ERROR.name());
            result.setResponseMessage(e.getMessage());
            result.setSuccess(false);
            // 返回错误响应
            return result;
        }
        // 解析响应字符串为链式响应对象并返回
        return JSON.parseObject(responseStr, ChainResponse.class);
    }

    /**
     * 执行查询操作的实现方法
     * 该方法使用GET请求向区块链服务查询数据，并处理响应结果
     *
     * @param chainRequest 包含请求信息的ChainRequest对象，包括主机地址、路径、签名等
     * @return 返回ChainResponse对象，包含响应代码、消息和查询结果
     */
    @Override
    protected ChainResponse doGetQuery(ChainRequest chainRequest) {
        // 构建GET请求URL，包含主机地址和路径
        HttpRequest get = HttpRequest.get(chainRequest.getHost() + chainRequest.getPath());
        // 添加请求头，包括签名、当前时间和API密钥
        get.addHeaders(configureHeaders(chainRequest.getSignature(), chainRequest.getCurrentTime(),
                wenChangChainConfiguration.getApiKey()));

        // 初始化响应字符串
        String responseStr = "";
        // 创建ChainResponse对象以存储最终结果
        ChainResponse result = new ChainResponse();
        try {
            // 发送GET请求并获取响应体内容
            responseStr = get.execute().body();
        } catch (Exception e) {
            // 记录错误日志，包括请求和响应内容
            log.error("http client error, request: {}, response: {}", chainRequest, responseStr);
            // 设置错误的响应代码、消息和成功状态为false
            result.setResponseCode(ChainCodeEnum.CHAIN_POST_ERROR.name());
            result.setResponseMessage(e.getMessage());
            result.setSuccess(false);
            // 返回错误结果
            return result;
        }
        // 解析响应字符串为ChainResponse对象并返回
        return JSON.parseObject(responseStr, ChainResponse.class);
    }

    @Override
    protected String chainType() {
        return ChainType.WEN_CHANG.name();
    }

    /**
     * 创建用户地址
     *
     * 该方法用于处理用户地址的创建请求它首先设置业务ID和业务类型，
     * 然后构造一个请求体，包括应用名称、操作ID等信息，最后发送请求并处理响应
     *
     * @param chainProcessRequest 包含用户ID、标识符等信息的请求对象
     * @return 返回一个包含链创建数据的响应对象
     */
    @Override
    public ChainProcessResponse<ChainCreateData> createAddr(ChainProcessRequest chainProcessRequest) {
        // 设置业务ID为用户ID，以便在业务系统中唯一标识此次操作
        chainProcessRequest.setBizId(chainProcessRequest.getUserId());
        // 设置业务类型为用户类型，用于区分不同的业务场景
        chainProcessRequest.setBizType(ChainOperateBizTypeEnum.USER.name());

        // 构造请求体，包括应用名称、用户ID等信息
        WenChangCreateBody body = new WenChangCreateBody();
        body.setName(APP_NAME_UPPER + SEPARATOR + ChainOperateBizTypeEnum.USER + SEPARATOR + chainProcessRequest.getUserId());
        body.setOperationId(chainProcessRequest.getIdentifier());
        String path = "/v3/account";

        // 获取当前时间戳，用于请求签名
        Long currentTime = System.currentTimeMillis();
        // 生成请求签名，确保请求的安全性和完整性
        String signature = WenChangChainUtils.signRequest(path, body, currentTime, wenChangChainConfiguration.getApiSecret());

        // 执行POST请求，并处理响应
        ChainProcessResponse response = doPostExecute(chainProcessRequest, ChainOperateTypeEnum.USER_CREATE, chainRequest -> chainRequest.build(
                body, path, signature, wenChangChainConfiguration.getHost(), currentTime
        ));

        // 如果响应成功且包含数据，则查询并更新链操作信息
        if (response.getSuccess() && response.getData() != null) {
            ChainOperateInfo chainOperateInfo = chainOperateInfoService.queryByOutBizId(chainProcessRequest.getBizId(), chainProcessRequest.getBizType()
            , chainProcessRequest.getIdentifier());

            // 更新链操作结果为成功
            boolean updateResult = chainOperateInfoService.updateResult(chainOperateInfo.getId(), ChainOperateStateEnum.SUCCEED, null);

        }
        return response;
    }

    /**
     * 执行链上操作的通用方法
     *
     * 本方法根据传入的ChainProcessRequest对象构建链上操作请求，并发送给文昌链平台
     * 它主要负责构建请求体、生成签名、确定操作类型，并调用相应的链操作执行方法
     *
     * @param chainProcessRequest 链处理请求对象，包含链操作所需的各种参数
     * @return 返回链操作的结果数据，封装在ChainProcessResponse对象中
     */
    @Override
    public ChainProcessResponse<ChainOperationData> chain(ChainProcessRequest chainProcessRequest) {
        // 创建文昌链请求体对象
        WenChangChainBody body = new WenChangChainBody();
        // 设置请求体中的类名
        body.setName(chainProcessRequest.getClassName());
        // 设置请求体中的操作标识符
        body.setOperationId(chainProcessRequest.getIdentifier());
        // 设置请求体中的类ID，前缀为"nft"
        body.setClassId("nft" + chainProcessRequest.getClassId());
        // 设置请求体中的所有者地址为配置的超级链地址
        body.setOwner(wenChangChainConfiguration.getChainAddrSuper());
        // 定义API路径
        String path = "/v3/native/nft/classes";

        // 获取当前时间戳
        Long currentTime = System.currentTimeMillis();
        // 生成请求签名
        String signature = WenChangChainUtils.signRequest(path, body, currentTime, wenChangChainConfiguration.getApiSecret());

        // 根据业务类型确定链操作类型
        ChainOperateTypeEnum chainOperateTypeEnum;
        if (StringUtils.equals(ChainOperateBizTypeEnum.BLIND_BOX.name(), chainProcessRequest.getBizType())){
            chainOperateTypeEnum = ChainOperateTypeEnum.BLIND_BOX_CHAIN;
        } else {
            chainOperateTypeEnum = ChainOperateTypeEnum.COLLECTION_CHAIN;
        }
        // 执行链操作
        return doPostExecute(chainProcessRequest, chainOperateTypeEnum, chainRequest -> chainRequest.build(body, path, signature, wenChangChainConfiguration.getHost(), currentTime));
    }

    /**
     * 执行数字资产铸造的函数
     * 该函数用于在区块链上创建新的数字资产实例，根据给定的请求参数构造铸造请求，并发送到文昌链
     *
     * @param chainProcessRequest 包含铸造请求的必要信息，如类ID、序列号、接收者等
     * @return 返回包含铸造操作结果的响应对象
     */
    @Override
    public ChainProcessResponse<ChainOperationData> mint(ChainProcessRequest chainProcessRequest) {
        // 构造文昌链铸造请求的主体内容
        WenChangMintBody body = new WenChangMintBody();
        // 设置资产名称，通常由类名和序列号组成，以便于识别
        body.setName(chainProcessRequest.getClassName() + "#" + chainProcessRequest.getSerialNo());
        // 设置资产的接收者地址
        body.setRecipient(chainProcessRequest.getRecipient());
        // 设置操作ID，用于跟踪和识别特定的铸造操作
        body.setOperationId(chainProcessRequest.getIdentifier());

        // 构造请求路径，包括API版本、资源类型和类ID
        String path = "/v3/native/nft/nfts/" + "nft" + chainProcessRequest.getClassId();

        // 获取当前时间戳，用于请求签名
        Long currentTime = System.currentTimeMillis();

        // 生成请求签名，确保请求的完整性和安全性
        String signature = WenChangChainUtils.signRequest(path, body, currentTime, wenChangChainConfiguration.getApiSecret());

        // 执行铸造操作，并返回结果
        // 这里使用了策略模式，将具体的请求构建和发送逻辑封装在chainRequest对象中
        return doPostExecute(chainProcessRequest, ChainOperateTypeEnum.COLLECTION_MINT, chainRequest -> chainRequest.build(body, path, signature, wenChangChainConfiguration.getHost(), currentTime));
    }

    /**
     * 执行NFT转让操作
     *
     * 该方法构建并发送一个NFT转让请求到文昌链，通过特定的API路径和签名机制确保请求的安全性和有效性
     *
     * @param chainProcessRequest 包含转让操作所需信息的请求对象，如接收者地址、操作标识、类ID、所有者地址和NFT ID
     * @return 返回一个包含链操作数据的响应对象，用于进一步处理或验证操作结果
     */
    @Override
    public ChainProcessResponse<ChainOperationData> transfer(ChainProcessRequest chainProcessRequest) {
        // 创建转让请求体对象，并设置接收者地址和操作标识
        WenChangTransferBody body = new WenChangTransferBody();
        body.setRecipient(chainProcessRequest.getRecipient());
        body.setOperationId(chainProcessRequest.getIdentifier());

        // 构建API路径，用于指定NFT转让的具体资源位置
        String path = "/v3/native/nft/nft-transfers/" + "nft" + chainProcessRequest.getClassId() + "/" +
                chainProcessRequest.getOwner() + "/" + chainProcessRequest.getNftId();

        // 获取当前时间戳，用于请求签名
        Long currentTime = System.currentTimeMillis();

        // 生成请求签名，确保请求的完整性和安全性
        String signature = WenChangChainUtils.signRequest(path, body, currentTime, wenChangChainConfiguration.getApiSecret());

        // 执行POST请求并处理响应，返回包含链操作数据的响应对象
        return doPostExecute(chainProcessRequest, ChainOperateTypeEnum.COLLECTION_TRANSFER, chainRequest -> chainRequest.build(body, path, signature, wenChangChainConfiguration.getHost(), currentTime));
    }

    /**
     * 重写destroy方法以处理链路销毁请求
     * 该方法构建了一个销毁链路操作的请求体，生成签名，并调用delete操作来销毁链路
     *
     * @param chainProcessRequest 包含销毁请求的必要信息，如标识符、类别ID、所有者和NFT ID
     * @return 返回一个包含链路操作数据的响应对象
     */
    @Override
    public ChainProcessResponse<ChainOperationData> destroy(ChainProcessRequest chainProcessRequest) {
        // 创建请求体对象
        WenChangRequestBody body = new WenChangRequestBody();
        // 设置请求体中的操作ID
        body.setOperationId(chainProcessRequest.getIdentifier());
        // 构建请求路径，包含NFT类别ID、所有者和NFT ID
        String path = "/v3/native/nft/nfts/" + "nft" + chainProcessRequest.getClassId() + "/"
                + chainProcessRequest.getOwner() + "/" + chainProcessRequest.getNftId();

        // 获取当前时间戳
        Long currentTime = System.currentTimeMillis();
        // 生成请求签名，确保请求的安全性和完整性
        String signature = WenChangChainUtils.signRequest(path, body, currentTime,
                wenChangChainConfiguration.getApiSecret());

        // 执行delete操作，传递请求体、路径、签名等必要信息
        return doDeleteExecute(chainProcessRequest, chainRequest -> chainRequest.build(body, path, signature, wenChangChainConfiguration.getHost(), currentTime));
    }

    /**
     * 查询链上操作结果
     *
     * 该方法用于向文昌链查询操作结果，包括交易哈希和NFT ID等信息
     * 它首先记录操作信息，然后构造请求并发送查询，最后处理查询结果并更新操作信息
     *
     * @param chainQueryRequest 包含查询所需信息的请求对象
     * @return 返回包含查询结果的响应对象
     * @throws SystemException 如果更新操作信息失败，抛出系统异常
     */
    @Override
    public ChainProcessResponse<ChainResultData> queryChainResult(ChainQueryRequest chainQueryRequest) {
        // 记录链操作信息
        var operateInfoId = chainOperateInfoService.insertInfo(ChainType.WEN_CHANG.name(),
                chainQueryRequest.getOperationInfoId(), ChainOperateBizTypeEnum.CHAIN_OPERATION.name(), ChainOperateTypeEnum.COLLECTION_QUERY.name(),
                JSON.toJSONString(chainQueryRequest), chainQueryRequest.getOperationId());

        // 构造请求路径和签名
        String path = "/v3/native/tx/" + chainQueryRequest.getOperationId();
        Long currentTime = System.currentTimeMillis();
        String signature = WenChangChainUtils.signRequest(path, null, currentTime,
                wenChangChainConfiguration.getApiSecret());

        // 创建并构建链请求对象
        ChainRequest chainRequest = new ChainRequest();
        chainRequest.build(null, path, signature, wenChangChainConfiguration.getHost(), currentTime);

        // 发送查询请求并接收响应
        ChainResponse result = doGetQuery(chainRequest);
        log.info("wen chang query result:{}", result);

        // 更新链操作信息的结果
        boolean updateResult = chainOperateInfoService.updateResult(operateInfoId,
                ChainOperateStateEnum.SUCCEED,
                result.getSuccess() ? result.getData().toString() : result.getError().toString());

        // 如果更新失败，抛出异常
        if (!updateResult) {
            throw new SystemException(RepoErrorCode.UPDATE_FAILED);
        }

        // 创建链处理响应对象并设置基本信息
        ChainProcessResponse<ChainResultData> response = new ChainProcessResponse<>();
        response.setSuccess(result.getSuccess());
        response.setResponseCode(result.getResponseCode());
        response.setResponseMessage(result.getResponseMessage());

        // 如果查询成功，处理并设置查询结果数据
        if (result.getSuccess()) {
            String txHash = result.getData().getString("tx_hash");
            String status = result.getData().getString("status");
            var nft = (HashMap) result.getData().get("nft");
            String nftId = (String) nft.get("id");

            ChainResultData data = new ChainResultData();
            data.setTxHash(txHash);
            data.setNftId(nftId);

            // 根据状态码设置操作状态
            switch (status) {
                case "0":
                    data.setState(ChainOperateStateEnum.PROCESSING.name());
                    break;
                case "1":
                    data.setState(ChainOperateStateEnum.SUCCEED.name());
                    break;
                case "2":
                    data.setState(ChainOperateStateEnum.FAILED.name());
                    break;
                case "3":
                    data.setState(ChainOperateStateEnum.INIT.name());
                    break;
                default:
                    throw new IllegalStateException("Unexpected value: " + status);
            }
            response.setData(data);
        }
        // 返回链处理响应对象
        return response;
    }
}
